﻿using AutoMapper;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Mvc;
using Microsoft.Data.Entity;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.PlatformAbstractions;
using Sino.CapacityCloud.Infrastructure.Features;
using Sino.CapacityCloud.Repositories.Models;
using Sino.CapacityCloud.Services;
using Sino.CapacityCloud.WebApi.ViewModels;
using Sino.Tms.Primitives;
using Sino.Tms.WebApi.Framework.Primitives;
using Sino.WebApi.Framework.Responses;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Z.EntityFramework.Plus;

namespace Sino.CapacityCloud.WebApi.ApiControllers
{

    public partial class UserController
    {

        private bool CheckIfCarCodeValid(string carCode)
        {
            // TODO: Make a query to the TMS to check if the given car code is valid.
            var regx = new Regex(@"^[\u4e00-\u9fa5]{1}[a-zA-Z]{1}[a-zA-Z_0-9]{4}[a-zA-Z_0-9_\u4e00-\u9fa5]$|^[a-zA-Z]{2}\d{7}$");
            return regx.IsMatch(carCode);
        }

        private bool IsCarPresentForUser(ApplicationUser user, string carCode = null)
        {
            if (user.Car == null)
            {
                user.Car = this.DataContext.Cars.FirstOrDefault(x => x.CarId == user.CarId);
            }
            if (user.Car == null)
            {
                // Try to find a car which matches the given car code.
                if (!string.IsNullOrEmpty(carCode))
                {
                    user.Car = this.DataContext.Cars.FirstOrDefault(x => x.Code == carCode);
                }
                if (user.Car == null)
                {
                    throw new SystemException($"Cannot find a car for the user {user.Id} Please do a real name operation first to have a car registered.");
                }
            }
            return true;
        }

        private async Task UpdateUserInfoAsync(ApplicationUser user, UserInfo info, string propertyName, bool mutable = false)
        {
            if (!mutable && propertyName == nameof(UserInfo.RealName) && user.IsCertificated())
            {
                throw new ArgumentException("The user has been certificated. A change to the certification information is not allowed.");
            }
            switch (propertyName)
            {
                case nameof(UserInfo.RealName):
                case nameof(UserInfo.Address):
                case nameof(UserInfo.HeadImage):
                case nameof(UserInfo.Profile):
                case nameof(UserInfo.CarriageType):
                    info.MapSingle(user, propertyName);
                    break;
                case nameof(UserInfo.CarLength):
                case nameof(UserInfo.CarType):
                case nameof(UserInfo.AuthorizedLoad):
                    if (user.Car == null)
                    {
                        user.Car = this.DataContext.Cars.FirstOrDefault(x => x.CarId == user.CarId);
                        if (user.Car == null)
                        {
                            throw new ArgumentException($"The user {user.Id} has not registered a car.");
                        }
                    }
                    info.MapSingle(user.Car, propertyName);
                    this.DataContext.SaveChanges();
                    break;
                default:
                    throw new ArgumentOutOfRangeException(nameof(propertyName), $"Property '{propertyName}' is not allowed to be modified.");
            }

            var identityResult = await this.UserManager.UpdateAsync(user);
            if (!identityResult.Succeeded)
            {
                var identityErrors = identityResult.Errors.Select(x => $"Code: {x.Code}, Description: {x.Description}").ToList();
                throw new Exception(string.Join(Environment.NewLine, identityErrors));
            }

            if (propertyName == nameof(UserInfo.HeadImage) || propertyName == nameof(UserInfo.RealName))
            {
                await this._chattingService.RefreshUserInfoAsync(user.Id, user.RealName ?? user.PhoneNumber, user.AvatarKey);
            }
        }

        private async Task UpdateUserInfoAsync(ApplicationUser user, UserInfo info, bool mutable = false)
        {
            if (!string.IsNullOrEmpty(info.CarLength))
            {
                this.IsCarPresentForUser(user);
                user.Car.Length = SystemUtils.Try(() => info.CarLength.ToEnumValue<CarLength, EnumTextMetaAttribute>(x => x.Text), CarLength.None);
            }
            if (!string.IsNullOrEmpty(info.CarType))
            {
                this.IsCarPresentForUser(user);
                user.Car.Type = SystemUtils.Try(() => info.CarType.ToEnumValue<VehicleType, EnumTextMetaAttribute>(x => x.Text), VehicleType.None);
            }
            if (!string.IsNullOrEmpty(info.CarNumber))
            {
                this.IsCarPresentForUser(user, info.CarNumber);
                user.Car.Code = info.CarNumber;
            }
            if (info.AuthorizedLoad > 0)
            {
                this.IsCarPresentForUser(user);
                user.Car.AssignmentOfLoad = info.AuthorizedLoad;
            }

            user.Address = info.Address ?? user.Address;
            user.CarriageType = (CarriageType)(info.CarriageType == CarriageType.None ? ((CarriageType)user.CarriageType) : info.CarriageType);
            user.Profile = info.Profile ?? user.Profile;
            user.AvatarKey = info.HeadImage;

            var identityResult = await this.UserManager.UpdateAsync(user);
            if (!identityResult.Succeeded)
            {
                var identityErrors = identityResult.Errors.Select(x => $"Code: {x.Code}, Description: {x.Description}").ToList();
                throw new Exception(string.Join(Environment.NewLine, identityErrors));
            }

            if (!mutable && info.RealName != user.RealName && user.IsCertificated())
            {
                throw new ArgumentException("The user has been certificated. A change to the certification information is not allowed.");
            }
            else
            {
                user.RealName = info.RealName ?? user.RealName;
            }

            identityResult = await this.UserManager.UpdateAsync(user);
            if (!identityResult.Succeeded)
            {
                var identityErrors = identityResult.Errors.Select(x => $"Code: {x.Code}, Description: {x.Description}").ToList();
                throw new Exception(string.Join(Environment.NewLine, identityErrors));
            }
        }

        private void UpdateUserRealNameInfo(ApplicationUser user, RealNameType type, string value, bool mutable = false)
        {
            if (string.IsNullOrEmpty(value) && !mutable)
            {
                throw new ArgumentNullException(nameof(value));
            }
            switch (type)
            {
                case RealNameType.RealName:
                    if (!mutable && !string.IsNullOrWhiteSpace(user.RealName))
                    {
                        throw new ArgumentException($"The real name for user {user.Id} has been certificated. A change to the certification information is not allowed.");
                    }
                    user.RealName = value;
                    break;

                case RealNameType.CarCode:
                    if (user.Car == null)
                    {
                        user.Car = this.DataContext.Cars.FirstOrDefault(x => x.CarId == user.CarId);
                        if (user.Car == null)
                        {
                            var car = new Repositories.Models.Car()
                            {
                                Code = value,
                                Date = DateTime.Now
                            };
                            this.DataContext.Cars.Add(car);
                            this.DataContext.SaveChanges();

                            user.Car = car;
                            this.DataContext.SaveChanges();
                            return;
                        }
                    }
                    if (mutable && user.Car.Code != value)
                    {
                        var anotherCar = this.DataContext.Cars.FirstOrDefault(x => x.Code == value);
                        if (anotherCar != null)
                        {
                            user.Car = anotherCar;
                        }
                        else
                        {
                            user.Car = null;
                            goto case RealNameType.CarCode;
                        }
                    }
                    break;

                case RealNameType.DrivingLicense:
                    if (!mutable && user.DrivingLicenseKeyCertificationState == CertificationStatus.Passed)
                    {
                        throw new ArgumentException($"The driving license for user {user.Id} has been certificated. A change to the certification information is not allowed.");
                    }
                    user.DrivingLicenseKey = value;
                    if (value == null)
                    {
                        user.DrivingLicenseKeyCertificationState = CertificationStatus.Uncommitted;
                    }
                    else if (value != null)
                    {
                        user.DrivingLicenseKeyCertificationState = CertificationStatus.Pending;
                        user.CertificationApplicationTime = DateTime.Now;
                    }
                    break;

                case RealNameType.IdCard:
                    if (user.IdentityCardCertificationState == CertificationStatus.Passed)
                    {
                        throw new ArgumentException($"The identity card for user {user.Id} has been certificated. A change to the certification information is not allowed.");
                    }
                    user.IdentityCardKey = value;
                    if (value == null)
                    {
                        user.IdentityCardCertificationState = CertificationStatus.Uncommitted;
                    }
                    else if (value != null)
                    {
                        user.IdentityCardCertificationState = CertificationStatus.Pending;
                    }
                    break;

                case RealNameType.RealHead:
                    if (!mutable && user.CertificatePhotoCertificationState == CertificationStatus.Passed)
                    {
                        throw new ArgumentException($"The avatar for user {user.Id} has been certificated. A change to the certification information is not allowed.");
                    }
                    user.CertificatePhotoKey = value;
                    if (value == null)
                    {
                        user.CertificatePhotoCertificationState = CertificationStatus.Uncommitted;
                    }
                    else if (value != null)
                    {
                        user.CertificatePhotoCertificationState = CertificationStatus.Pending;
                        user.CertificationApplicationTime = DateTime.Now;
                    }
                    break;

                case RealNameType.VehicleLicense:
                    if (user.Car == null)
                    {
                        user.Car = this.DataContext.Cars.FirstOrDefault(x => x.CarId == user.CarId);
                        if (user.Car == null)
                        {
                            throw new SystemException($"Cannot find a car for the user {user.Id}.");
                        }
                    }
                    if (!mutable && user.Car.CertificationStatus == CertificationStatus.Passed)
                    {
                        throw new ArgumentException($"The vehicle license for user {user.Id} has been certificated. A change to the certification information is not allowed.");
                    }
                    user.Car.VehicleLicenseKey = value;
                    if (value == null)
                    {
                        user.Car.CertificationStatus = CertificationStatus.Uncommitted;
                    }
                    else if (value != null)
                    {
                        user.Car.CertificationStatus = CertificationStatus.Pending;
                        user.CertificationApplicationTime = DateTime.Now;
                    }
                    break;
                case RealNameType.TrailerLicense:
                    if (user.Car == null)
                    {
                        user.Car = this.DataContext.Cars.FirstOrDefault(x => x.CarId == user.CarId);
                        if (user.Car == null)
                        {
                            throw new SystemException($"Cannot find a car for the user {user.Id}.");
                        }
                    }
                    if (!mutable && user.Car.TrailerLicenseCertificationStatus == CertificationStatus.Passed)
                    {
                        throw new ArgumentException($"The Trailer license for user {user.Id} has been certificated. A change to the certification information is not allowed.");
                    }
                    user.Car.TrailerLicenseKey = value;
                    if (value == null)
                    {
                        user.Car.TrailerLicenseCertificationStatus = CertificationStatus.Uncommitted;
                    }
                    else if (value != null)
                    {
                        user.Car.TrailerLicenseCertificationStatus = CertificationStatus.Pending;
                    }
                    break;
                case RealNameType.VehiclePhoto:
                    if (user.Car == null)
                    {
                        user.Car = this.DataContext.Cars.FirstOrDefault(x => x.CarId == user.CarId);
                        if (user.Car == null)
                        {
                            throw new SystemException($"Cannot find a car for the user {user.Id}.");
                        }
                    }
                    if (!mutable && user.Car.VehiclePhotoCertificationStatus == CertificationStatus.Passed)
                    {
                        throw new ArgumentException($"The Vehicle Photo for user {user.Id} has been certificated. A change to the certification information is not allowed.");
                    }
                    user.Car.VehiclePhotoKey = value;
                    if (value == null)
                    {
                        user.Car.VehiclePhotoCertificationStatus = CertificationStatus.Uncommitted;
                    }
                    else if (value != null)
                    {
                        user.Car.VehiclePhotoCertificationStatus = CertificationStatus.Pending;
                    }
                    break;
                case RealNameType.None:
                default:
                    break;
            }
        }

    }

}